An I/O controller, such as a disk controller or a network controller, typically moves data between a disk subsystem or a network subsystem, and other units of a computer system. When inbound data is received from the subsystem, the controller typically transfers the data to a main memory unit, to await further processing. The memory unit may be connected to a system bus, which is shared by other units of the computer connected thereto. Outbound data, retrieved from the memory unit, are subsequently transferred by the controller to the subsystem. A DMA function located on the controller directs the movement of data through the controller. Within the DMA function, there is typically a set of data paths and a control function, the latter often being implemented by a state machine. The data paths and the state machine may also be shared between the inbound and outbound data transfers. Consequently, the main memory, the system bus and the DMA function may be considered shared resources requiring arbitration to gain access to effectuate data transfers in different directions.
The controller is typically configured for half-duplex communication with the subsystem, i.e., it is capable of either transmitting and receiving data at any given time, but not simultaneously. However, the controller can be configured for full-duplex communication to transmit outbound data and receive inbound data at the same time. For either type of communication, the controller must be capable of transmitting and receiving "blocks" of data to and from the subsystem. A block of data may represent a fixed group of data for the disk or a fixed packet of data for the network.
Inbound data received from the subsystem are moved through the controller and onto the system bus in accordance with direct memory access (DMA) transfers on the bus. A DMA transfer typically involves (i) a request for access to a shared resource followed by a subsequent grant of access, and (ii) a "burst" of data of a predetermined size. Because it may be busy performing operations with other units of the computer system, the shared resource is not always immediately available for use. The time between when a request is issued to access the shared resource and when access to that resource is granted constitutes "latency" of the resource. Typically, this latency varies from system to system, depending upon the characteristics of the resource, e.g., the throughput of the system bus and the number of units connected to the bus.
A known solution to the variable latency problem is to provide a buffer memory in the controller, i.e., between the subsystem and the system bus. Because of cost and design constraints, the size of the buffer memory is typically small. For example, when implementing an Ethernet controller in a single chip, the size of the buffer memory may be between 48-256 bytes. The buffer can continue to receive data from the subsystem when access to the shared resource has yet to be granted. When access to the resource is subsequently granted, the controller can then transfer a burst of data from the buffer. There is, of course, the possibility of a buffer overflow condition, when access to the shared resource is not granted for an interval long enough that the buffer becomes completely filled.
A similar problem is presented during transfer of outbound data to the subsystem. Once access to the subsystem is granted, a block of data, e.g., a packet, must be transferred at a predetermined rate to the disk subsystem or the network subsystem. Another buffer memory in the transmit path of the controller stores at least a limited amount of data so that a steady stream of outbound data may continue when access to the shared resource has not been granted. In this case, there is the possibility of buffer underflow, when the buffer is completely emptied by the need to transmit data at a fixed rate for the duration of the data block and the shared resource cannot be accessed.
One conventional simple policy for managing these buffers ensures reception of data from the subsystems at the expense of data transmission thereto. In other words, if a receive operation is in progress, priority is given to the completion of that operation, i.e., reception of a complete block of data at the controller, before any attention is given to possible transmit operations. Moreover, if another incoming block is recognized before the transmit operation begins, the latter operation may be aborted. The receive operations can thus completely monopolize the controller and transmit operations may be delayed indefinitely, resulting in "transmission starvation".
Another conventional policy merely alternates between transmit and receive operations, without regard as to the status conditions of the shared resource and the buffers. For example, transmission to a subsystem may be initiated even though the shared resource cannot be accessed and there is an insufficient amount of data stored in the buffer to transmit data at the required steady rate for the duration of the data block. The resulting buffer underflow situation degrades the performance of transmission of packets because only fragments of the packet can be transmitted to the subsystem. Similarly, an overflow from the buffer receiving data from the subsystem may be more frequent when a simple alternating policy is used.
Each of these arrangements suffers from the disadvantage that the controller cannot tolerate a relatively long latency and low throughput of the shared resource compared with the data transfer rate of the controller. Consequently, the controller or the system is designed to meet tight timing requirements for accessing the resource. Such restrictions also increase the cost and complexity of the system, while limiting the number of the controllers that may be employed in the system.
It is therefore apparent that there exists a need for enabling efficient access to the shared resource while managing the buffers for data transfer through the controller.